package treeedit;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

public class Tree implements Cloneable {
	private int id = -1;
	private String name;
	private List<Tree> children;
	private final static String NONAME = "";

	public Tree(String name) {
		this.name = name;
	}

	public Tree() {
		this.name = NONAME;
	}

	public Tree addChild(Tree child) {
		if (children == null)
			children = new ArrayList<Tree>();
		children.add(child);
		return child;
	}

	public String getName() {
		return name;
	}

	public int getId() {
		return id;
	}

	public Tree getFirstChild() {
		return children.get(0);
	}

	public boolean isLeaf() {
		return children == null || children.size() == 0;
	}

	public List<Tree> getChildren() {
		return children;
	}

	public int getSize() {
		int size = 1;
		if (!isLeaf())
			for (Tree child : children)
				size += child.getSize();
		return size;
	}

	public int getLeavesSize() {
		int size;
		if (isLeaf())
			size = 1;
		else {
			size = 0;
			for (Tree child : children)
				size += child.getLeavesSize();
		}
		return size;
	}

	public int computeId(int treeId) {
		if (isLeaf())
			id = treeId;
		else {
			for (Tree child : children)
				treeId = child.computeId(treeId) + 1;
			id = treeId;
		}
		return id;
	}

	public void getHashId(Hashtable<Integer, Tree> hash) {
		hash.put(id, this);
		if (!isLeaf())
			for (Tree child : children)
				child.getHashId(hash);
	}

	public String debugOutputThis() {
		return getName() + "[" + getLeavesSize() + "]";
	}

	public String debugOutput() {
		return debugOutput(0);
	}

	private String debugOutput(int indent) {
		StringBuilder sb = new StringBuilder();
		String ind = Utils.getIndent(indent);

		sb.append(String.format("%s%s\n", ind, getName()));

		if (children != null && children.size() > 0)
			for (Tree child : children) {
				sb.append(child.debugOutput(indent + 1));
			}

		return sb.toString();
	}

	public void setName(String name) {
		this.name = name;

	}

	/**
	 * Compares the names of this Tree node and another tree.
	 */
	public boolean compareNames(Tree t) {
		return getName().equals(t.getName());
	}

	/**
	 * Compares the children of this Tree node and another tree.
	 */
	public boolean compareChildren(Tree t) {
		if (this.children == null) {
			if (t.children == null) {
				return true;
			} else {
				return false;
			}
		} else {
			if (t.children == null) {
				return false;
			}
		}

		// if the number of children differs, they are not equal
		if (this.children.size() != t.children.size())
			return false;

		boolean result = true;
		Tree tempChild;

		// for each child of this node
		for (Tree child : children) {
			// the other tree contains child with same name
			if ((tempChild = t.getChildNamed(child.getName())) != null) {
				// compare these two nodes
				result &= child.compareChildren(tempChild);
			} else {
				// no child with this name
				return false;
			}
		}
		return result;
	}

	public Tree getChildNamed(String childName) {
		for (Tree child : children) {
			if (child.getName().equals(childName))
				return child;
		}
		return null;
	}

	public boolean equals(Tree t) {
		return compareNames(t) && compareChildren(t);
	}

	public void removeChild(int position) {
		if (children.size() < position)
			return;

		// find the one to remove
		Tree removeThis = children.get(position);

		// remove it
		children.remove(position);

		// adopt all of his children
		for (Tree child : removeThis.getChildren()) {
			this.children.add(position, child);
		}
	}

	public void removeThis(Tree parent) {
		removeThis(parent, parent.children.indexOf(this));
	}

	public void removeThis(Tree parent, int appendToThisPosition) {
		// position higher than number of children, append to end
		if (parent.children.size() < appendToThisPosition)
			appendToThisPosition = parent.children.size();

		// do I have children?
		if (this.getChildren() != null) {
			// adopt all of mine children
			for (Tree child : this.getChildren()) {
				if (parent.children != null) {
					parent.children.add(appendToThisPosition, child);
					appendToThisPosition++;
				}
			}
		}
		parent.children.remove(this);
	}

	@Override
	public Object clone() {
		Tree newTree = new Tree(this.getName());
		try {
			if (this.children != null) {
				for (Tree child : this.children) {
					newTree.addChild((Tree) child.clone());
				}
			}
		} catch (Exception e) {
			return null;
		}
		return newTree;
	}

	/**
	 * Initialization method, must be called before TED can be calculated.
	 */
	public void init() {
		this.computeId(1);
	}
}
